package othello.client.playroom;

import java.util.HashMap;
import java.util.Map;

import othello.client.utility.screen.Drawable;
import othello.client.utility.screen.DrawableNone;
import othello.client.utility.screen.Screen;
import othello.client.utility.screen.View;
import othello.shared.model.GameStep;
import othello.shared.model.GameStep.Side;

import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;

/**
 * <p>
 * ゲーム盤の状態を表示するクラス
 * </p>
 * <p>
 * 当クラスはインスタンス生成時に指定されたゲーム盤モデルの状態の変更を監視して、画像の更新のトリガーとします。
 * </p>
 * 
 * @author onjiro.mohyahya
 */
class ViewForGameBoard extends View implements GameBoard.Observer {

	/**
	 * 石の表示マスの画像状態の配列
	 */
	private final ViewForGameBoard.CellImage[][] cells;

	/**
	 * 盤面のイメージ
	 */
	private final Drawable boardImage;

	/**
	 * 石の各表面の画像イメージ
	 */
	private final Map<GameStep.Side, Drawable> cellImages = new HashMap<GameStep.Side, Drawable>();

	/**
	 * 位置情報の変換器
	 */
	private final ImageSettings.PositionConvertor positionConvertor;

	/**
	 * <p>
	 * ゲーム盤のモデルを引数に取るコンストラクター
	 * </p>
	 * 
	 * @param boardModel
	 *            状態の表示対象のゲーム盤
	 */
	public ViewForGameBoard(GameBoard boardModel, ImageSettings images) {
		boardModel.addObserver(this);
		this.positionConvertor = images.getPositionConvertor();

		Side[][] boardStates = boardModel.getCurrentBoard();
		final int width = boardStates.length;
		final int height = boardStates[0].length;
		this.cells = new ViewForGameBoard.CellImage[width][height];

		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				Position position = positionConvertor.toPixelPosition(x, y);
				this.cells[x][y] = new CellImage(position.x, position.y);
			}
		}

		// 画像の設定
		this.boardImage = images.getImages().get(ImageSettings.ImageKey.Board);
		this.cellImages.put(Side.Black, images.getImages().get(
				ImageSettings.ImageKey.BlackStone));
		this.cellImages.put(Side.White, images.getImages().get(
				ImageSettings.ImageKey.WhiteStone));
		this.cellImages.put(Side.None, DrawableNone.getInstance());
		this.notifyUpdate(boardModel);
	}

	/**
	 * <p>
	 * ゲーム盤の内容の変更の通知を受け取り、以下を実行します。
	 * <ul>
	 * <li>変更内容を適用し、内容物の表示状態を変更する。</li>
	 * <li>表示状態の変更があることをさらに当オブジェクトのObserverに通知する。</li>
	 * </ul>
	 * </p>
	 */
	@Override
	public void notifyUpdate(GameBoard updatedBoard) {
		// 表示内容の変更
		Side[][] currentBoardStates = updatedBoard.getCurrentBoard();
		final int width = currentBoardStates.length;
		final int height = currentBoardStates[0].length;

		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				this.cells[x][y].setImage(this.cellImages
						.get(currentBoardStates[x][y]));
			}
		}

		// ゲーム終了時にはその旨表示する。
		if (updatedBoard.isEnded()) {
			Side[][] board = updatedBoard.getCurrentBoard();
			int white = 0, black = 0;
			for (int x = 0; x < board.length; x++) {
				for (int y = 0; y < board[x].length; y++) {
					if (board[x][y] == Side.White) {
						white++;
					} else if (board[x][y] == Side.Black) {
						black++;
					}
				}
			}
			RootPanel.get().add(
					new Label((white < black) ? "Black win!!" : "White win!!"));
			RootPanel.get().add(
					new Label("White " + white + " : Black " + black));
			RootPanel.get().add(new HTML("<a href=\"/reset\">Reset</a>"));
		}

		// 表示状態に変更があることを通知する。
		super.notifyUpdate();
	}

	/**
	 * ゲーム盤の状態を指定のScreenに描画します。
	 */
	@Override
	public void draw(Screen screen) {
		// 盤面の描画
		this.boardImage.draw(screen, 0, 0);

		// それぞれの升目（石）の状態の描画
		for (int x = 0; x < this.cells.length; x++) {
			for (int y = 0; y < this.cells[x].length; y++) {
				this.cells[x][y].draw(screen);
			}
		}
	}

	/**
	 * <p>
	 * 個別のマスの表示状態を表すクラス。
	 * </p>
	 * <p>
	 * 生成時に指定した表示位置を指定します。表示の変更がある場合、新しい画像イメージを設定します。
	 * </p>
	 */
	private static class CellImage {

		/**
		 * 当インスタンスが画像を描画する座標
		 */
		private final int x, y;

		/**
		 * 表示画像、初期状態では何も表示しません。
		 */
		private Drawable image = DrawableNone.getInstance();

		/**
		 * 画像表示座標を指定するコンストラクター
		 * 
		 * @param x
		 *            画像表示位置(X座標)
		 * @param y
		 *            画像表示位置(Y座標)
		 */
		public CellImage(int x, int y) {
			this.x = x;
			this.y = y;
		}

		/**
		 * 画像を表示します。
		 * 
		 * @param screen
		 *            表示対象のScreen
		 */
		public void draw(Screen screen) {
			this.image.draw(screen, this.x, this.y);
		}

		/**
		 * 表示する画像を更新します。
		 * 
		 * @param newImage
		 *            新たに表示する画像
		 */
		public void setImage(Drawable newImage) {
			this.image = newImage;
		}

	}

}